<template>
  <div
    class="h-rate"
    :class="{ 'is-disabled': disabled, [`h-rate--${size}`]: size }"
    tabindex="0"
    :style="rateStyles"
  >
    <span
      v-for="(item, key) in max"
      :key="key"
      class="h-rate__item"
      @mousemove="setCurrentValue(item, $event)"
      @mouseleave="resetCurrentValue"
      @click="selectValue(item)"
    >
      <el-icon
        class="h-rate__icon"
        :class="[
          { hover: hoverIndex === item },
          { 'is-active': item <= currentValue! },
        ]"
      >
        <template v-if="!showDecimalIcon(item)">
          <component :is="activeComponent" v-show="item <= currentValue" />
          <component :is="voidComponent" v-show="!(item <= currentValue)" />
        </template>
        <template v-if="showDecimalIcon(item)">
          <component :is="voidComponent" class="h-rate__decimal" />
          <el-icon :style="decimalStyle" class="h-rate__icon h-rate__decimal">
            <component :is="decimalIconComponent" />
          </el-icon>
        </template>
      </el-icon>
    </span>
    <!-- <span
      v-if="showText || showScore"
      :class="ns.e('text')"
      :style="{ color: textColor }"
    >
      {{ text }}
    </span> -->
  </div>
</template>
<script lang="ts" setup>
import { computed, inject, markRaw, ref, watch } from "vue";
// import { EVENT_CODE, UPDATE_MODEL_EVENT } from "@element-plus/constants";
// import { hasClass, isArray, isObject, isString } from "@element-plus/utils";
// import {
//   formContextKey,
//   formItemContextKey,
//   useFormItemInputId,
//   useFormSize,
// } from "@element-plus/components/form";
// import { ElIcon } from "@element-plus/components/icon";
// import { useNamespace } from "@element-plus/hooks";
// import { rateEmits, rateProps } from "./rate";
import type { GradeProps, GradeEmits } from "./type";
import type { CSSProperties, Component } from "vue";
import { isArray, isObject, isString, some } from "lodash";
import { ElIcon } from "element-plus";
import { Star, StarFilled } from "@element-plus/icons-vue";

// 根据输入的数值 value 从一个特殊结构的映射表 map 中查找并返回匹配的值
function getValueFromMap<T>(
  value: number,
  map: Record<string, T | { excluded?: boolean; value: T }>
) {
  const isExcludedObject = (
    val: unknown
  ): val is { excluded?: boolean } & Record<any, unknown> => isObject(val);

  const matchedKeys = Object.keys(map)
    .map((key) => +key)
    .filter((key) => {
      const val = map[key];
      const excluded = isExcludedObject(val) ? val.excluded : false;
      return excluded ? value < key : value <= key;
    })
    .sort((a, b) => a - b);
  const matchedValue = map[matchedKeys[0]];
  return (isExcludedObject(matchedValue) && matchedValue.value) || matchedValue;
}



const {
  disabled,
  voidColor = "#c6d1de",
  disabledVoidColor = "#eff2f7",
  colors = ["#f7ba2a", "#f7ba2a", "#f7ba2a"],
  lowThreshold = 2,
  highThreshold = 4,
  max = 5,
  allowHalf = false,
  icons = [StarFilled, StarFilled, StarFilled],
  disabledVoidIcon = StarFilled,
  voidIcon = Star,
  clearable = false,
  size = "default",
} = defineProps<GradeProps>();
const emit = defineEmits<GradeEmits>();
const modelValue = defineModel<number>({ default: 0 });
const currentValue = ref(modelValue.value);

// const formContext = inject(formContextKey, undefined);
// const formItemContext = inject(formItemContextKey, undefined);
// const rateSize = useFormSize();
// const ns = useNamespace("rate");
// const { inputId, isLabeledByFormItem } = useFormItemInputId(props, {
//   formItemContext,
// });

// const currentValue = ref(props.modelValue);
const hoverIndex = ref(-1);
const pointerAtLeftHalf = ref(true);

// const rateClasses = computed(() => [ns.b(), ns.m(rateSize.value)]);
// const rateDisabled = computed(() => props.disabled || formContext?.disabled);

// 全选颜色
const rateStyles = computed(() => {
  return {
    "--h-rate-void-color": voidColor,
    "--h-rate-disabled-void-color": disabledVoidColor,
    "--h-rate-fill-color": activeColor.value,
  };
});

// const text = computed(() => {
//   let result = "";
//   if (props.showScore) {
//     result = props.scoreTemplate.replace(
//       /\{\s*value\s*\}/,
//       rateDisabled.value ? `${props.modelValue}` : `${currentValue.value}`
//     );
//   } else if (props.showText) {
//     result = props.texts[Math.ceil(currentValue.value) - 1];
//   }
//   return result;
// });

const valueDecimal = computed(
  () => modelValue.value! * 100 - Math.floor(modelValue.value!) * 100
);

// 颜色等级映射
const colorMap = computed(() =>
  isArray(colors)
    ? {
        [lowThreshold]: colors[0],
        [highThreshold]: { value: colors[1], excluded: true },
        [max]: colors[2],
      }
    : colors
);

// 计算当前活动颜色
const activeColor = computed(() => {
  const color = getValueFromMap(currentValue.value!, colorMap.value);
  // {value: '', excluded: true} returned
  return isObject(color) ? "" : color;
});

// 半选颜色
const decimalStyle = computed(() => {
  let width = "";
  if (disabled) {
    width = `${valueDecimal.value}%`;
  } else if (allowHalf) {
    width = "50%";
  }
  return {
    color: activeColor.value,
    width,
  };
});

const componentMap = computed(() => {
  let icons1 = isArray(icons) ? [...icons] : { ...icons };
  icons1 = markRaw(icons1) as
    | Array<string | Component>
    | Record<number, string | Component>;
  return isArray(icons1)
    ? {
        [lowThreshold]: icons1[0],
        [highThreshold]: {
          value: icons1[1],
          excluded: true,
        },
        [max]: icons1[2],
      }
    : icons1;
});

const decimalIconComponent = computed(() =>
  getValueFromMap(modelValue.value, componentMap.value)
);

const voidComponent = computed(() =>
  disabled
    ? isString(disabledVoidIcon)
      ? disabledVoidIcon
      : (markRaw(disabledVoidIcon) as Component)
    : isString(voidIcon)
    ? voidIcon
    : (markRaw(voidIcon) as Component)
);

const activeComponent = computed(() =>
  getValueFromMap(currentValue.value, componentMap.value)
);

function showDecimalIcon(item: number) {
  const showWhenDisabled =
    disabled &&
    valueDecimal.value > 0 &&
    item - 1 < modelValue.value &&
    item > modelValue.value;
  const showWhenAllowHalf =
    allowHalf &&
    pointerAtLeftHalf.value &&
    item - 0.5 <= currentValue.value &&
    item > currentValue.value;
  return showWhenDisabled || showWhenAllowHalf;
}

function emitValue(value: number) {
  // if allow clear, and selected value is same as modelValue, reset value to 0
  if (clearable && value === modelValue.value) {
    value = 0;
  }
  modelValue.value = value;
  if (modelValue.value !== value) {
    emit("change", value);
  }
}

// 选择等级
function selectValue(value: number) {
  if (disabled) {
    return;
  }
  if (allowHalf && pointerAtLeftHalf.value) {
    emitValue(currentValue.value);
  } else {
    emitValue(value);
  }
}

function setCurrentValue(value: number, event?: MouseEvent) {
  if (disabled) {
    return;
  }
  if (allowHalf && event) {
    // TODO: use cache via computed https://github.com/element-plus/element-plus/pull/5456#discussion_r786472092
    let target = event.target as HTMLElement;
    if (target.className === "h-rate__item") {
      target = target.querySelector("h-rate__icon")!;
    }
    console.log(target.clientWidth);
    
    if (target.clientWidth === 0 || target.className === "h-rate__decimal") {
      target = target.parentNode as HTMLElement;
    }
    pointerAtLeftHalf.value = event.offsetX * 2 <= target.clientWidth;
    currentValue.value = pointerAtLeftHalf.value ? value - 0.5 : value;
  } else {
    currentValue.value = value;
  }
  hoverIndex.value = value;
}

function resetCurrentValue() {
  if (disabled) {
    return;
  }
  if (allowHalf) {
    pointerAtLeftHalf.value = modelValue.value !== Math.floor(modelValue.value);
  }
  currentValue.value = modelValue.value;
  hoverIndex.value = -1;
}

watch(
  () => modelValue.value,
  (val) => {
    currentValue.value = val;
    pointerAtLeftHalf.value = modelValue.value !== Math.floor(modelValue.value);
  }
);

// if (!modelValue.value) {
//   emit(UPDATE_MODEL_EVENT, 0);
// }

defineExpose({
  setCurrentValue,
  resetCurrentValue,
});
</script>
